// Copyright 2019 Proyectos y Sistemas de Mantenimiento SL (eProsima).
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------
// Modification history:
// feature: Unified Transport Abstraction Layer Unified Transport Abstraction Layer
// feature: support multiple sending based on topic
// feature: In the case of white list configuration, only the same network segment is supported to send packets
// ------------------------------------------------------------------

#ifndef SRC_CPP_RTPS_TRANSPORT_UDPTRANSPORTINTERFACE_H_
#define SRC_CPP_RTPS_TRANSPORT_UDPTRANSPORTINTERFACE_H_

#include <thread>
#include <vector>
#include <memory>
#include <map>
#include <mutex>

#include <asio.hpp>

#include <deps/common/IPFinder.h>

#include <transport/TransportInterface.h>
#include <transport/UDPTransportDescriptor.h>

#include "UDPChannelResource.h"

namespace vbs {
namespace transport {
using namespace vbsutil::xmlparser;

class UDPTransportInterface : public TransportInterface {
    friend class UDPSenderResource;

 public:
    ~UDPTransportInterface() override;

    void clean();

    //! Removes the listening socket for the specified port.
    bool CloseInputChannel(const Locator_t&) override;

    //! Removes all outbound sockets on the given port.
    void CloseOutputChannel(vbsUDPSocket& socket);

#ifdef EDDS_XDP_TRANSPORT_COMPILE
    void CloseOutputChannel(LixdpSocket* lixdp_socket);
#endif
    //! Reports whether Locators correspond to the same port.
    bool DoInputLocatorsMatch(const Locator_t&, const Locator_t&) const override;

    virtual const UDPTransportDescriptor* configuration() const = 0;

    bool init() override;

    //! Checks whether there are open and bound sockets for the given port.
    bool IsInputChannelOpen(const Locator_t&) const override;

    //! Checks for TCP kinds.
    bool IsLocatorSupported(const Locator_t& locator) const override { return locator.kind == transport_kind_; }

    //! Opens a socket on the given address and port (as long as they are white listed).
    bool OpenOutputChannel(SendResourceList& sender_resource_list, const Locator_t&) override;
    bool OpenOutputChannel(std::map<Locator_t, std::shared_ptr<SenderResource>>&, const Locator_t&,
                           Locator_t&) override {
        return true;
    }

    /**
     * Converts a given remote locator (that is, a locator referring to a remote
     * destination) to the main local locator whose channel can write to that
     * destination. In this case it will return a 0.0.0.0 address on that port.
     */
    Locator RemoteToMainLocal(const Locator_t&) const override;

    /**
     * Transforms a remote locator into a locator optimized for local communications.
     *
     * If the remote locator corresponds to one of the local interfaces, it is converted
     * to the corresponding local address.
     *
     * @param [in]  remote_locator Locator to be converted.
     * @param [out] result_locator Converted locator.
     *
     * @return false if the input locator is not supported/allowed by this transport, true
     * otherwise.
     */
    bool transform_remote_locator(const Locator_t& remote_locator, Locator_t& result_locator) const override;

    /**
     * Blocking Send through the specified channel. In both modes, using a localLocator of 0.0.0.0
     * will send through all whitelisted interfaces provided the channel is open.
     * @param send_buffer Slice into the raw data to send.
     * @param send_buffer_size Size of the raw data. It will be used as a bounds check for the
     * previous argument. It must not exceed the send_buffer_size fed to this class during
     * construction.
     * @param socket channel we're sending from.
     * @param destination_locators_begin pointer to destination locators iterator begin, the
     * iterator can be advanced inside this fuction so should not be reuse.
     * @param destination_locators_end pointer to destination locators iterator end, the iterator
     * can be advanced inside this fuction so should not be reuse.
     * @param only_multicast_purpose multicast network interface
     * @param whitelisted network interface included in the user whitelist
     * @param max_blocking_time_point maximum blocking time.
     */
    virtual bool send(unsigned char* send_buffer, uint32_t send_buffer_size, vbsUDPSocket& socket,
                      asio::ip::address& local_address, LocatorsIterator* destination_locators_begin,
                      LocatorsIterator* destination_locators_end, bool only_multicast_purpose, bool whitelisted,
                      uint32_t mask_len);

#ifdef EDDS_XDP_TRANSPORT_COMPILE
    bool send(unsigned char* send_buffer, uint32_t send_buffer_size, LixdpSocket* lixdp_socket,
              LocatorsIterator* destination_locators_begin, LocatorsIterator* destination_locators_end,
              bool only_multicast_purpose, bool whitelisted, uint32_t mask_len);
#endif

    bool select_multicast() const override { return true; }

    bool fillMetatrafficMulticastLocator(Locator_t& locator, uint32_t metatraffic_multicast_port) const override;

    bool fillMetatrafficUnicastLocator(Locator_t& locator, uint32_t metatraffic_unicast_port) const override;

    bool configureInitialPeerLocator(Locator_t& locator, const PortParameters& port_params, uint32_t domainId,
                                     LocatorList& list) const override;

    bool fillUnicastLocator(Locator_t& locator, uint32_t well_known_port) const override;

    uint32_t max_recv_buffer_size() const override { return configuration()->maxMessageSize; }

    void update_network_interfaces() override;
#ifdef EDDS_XDP_TRANSPORT_COMPILE
    SocketType get_socket_type() const { return socket_type_; }

    void set_socket_type(SocketType socket_type) { socket_type_ = socket_type; }
#endif
 protected:
    friend class UDPChannelResource;

    // For UDPv6, the notion of channel corresponds to a port + direction tuple.
    asio::io_service io_service_;
    std::vector<IPFinder::info_IP> currentInterfaces;

    mutable std::recursive_mutex mInputMapMutex;
    std::map<uint16_t, std::vector<UDPChannelResource*>> mInputSockets;

    uint32_t mSendBufferSize;
    uint32_t mReceiveBufferSize;

    //! First time open output channel flag: open the first socket with the
    //! ip::multicast::enable_loopback
    bool first_time_open_output_channel_;

    UDPTransportInterface(int32_t transport_kind);

    virtual bool compare_locator_ip(const Locator_t& lh, const Locator_t& rh) const = 0;
    virtual bool compare_locator_ip_and_port(const Locator_t& lh, const Locator_t& rh) const = 0;

    virtual void endpoint_to_locator(asio::ip::udp::endpoint& endpoint, Locator_t& locator) = 0;
    virtual void fill_local_ip(Locator_t& loc) const = 0;

    virtual asio::ip::udp::endpoint GenerateAnyAddressEndpoint(uint16_t port) = 0;
    virtual asio::ip::udp::endpoint generate_endpoint(uint16_t port) = 0;
    virtual asio::ip::udp::endpoint generate_endpoint(const std::string& sIp, uint16_t port) = 0;
    virtual asio::ip::udp::endpoint generate_endpoint(const Locator_t& loc, uint16_t port) = 0;
    virtual asio::ip::udp::endpoint generate_local_endpoint(const Locator_t& loc, uint16_t port) = 0;
    virtual asio::ip::udp generate_protocol() const = 0;
    virtual void get_ips(std::vector<IPFinder::info_IP>& locNames, bool return_loopback = false) = 0;
    virtual const std::string& localhost_name() = 0;

    //! Checks if the interfaces white list is empty.
    virtual bool is_interface_whitelist_empty() const = 0;

    //! Checks if the given interface is allowed by the white list.
    virtual bool is_interface_allowed(const std::string& interface) const = 0;
    virtual bool is_interface_allowed(const std::string& interface, uint32_t& order) const = 0;
    virtual void create_sender_resource_with_whitelist(SendResourceList& sender_resource_list, uint16_t port) = 0;
    /**
     * Method to get a list of interfaces to bind the socket associated to the given locator.
     * @return Vector of interfaces in string format.
     */
    virtual std::vector<std::string> get_binding_interfaces_list() = 0;

    void CloseInputChannelInterrupt(const Locator_t& locator);
    bool OpenAndBindInputSockets(const Locator_t& locator, std::shared_ptr<ReceiverResource> receiver,
                                 bool is_multicast, uint32_t maxMsgSize);
    UDPChannelResource* CreateInputChannelResource(const std::string& sInterface, const Locator_t& locator,
                                                   bool is_multicast, uint32_t maxMsgSize,
                                                   std::shared_ptr<ReceiverResource> receiver);
    virtual vbsUDPSocket OpenAndBindInputSocket(const std::string& sIp, uint16_t port, bool is_multicast) = 0;
    vbsUDPSocket OpenAndBindUnicastOutputSocket(const asio::ip::udp::endpoint& endpoint, uint16_t& port);

    virtual void set_receive_buffer_size(uint32_t size) = 0;
    virtual void set_send_buffer_size(uint32_t size) = 0;
    virtual void SetSocketOutboundInterface(vbsUDPSocket&, const std::string&) = 0;

    /**
     * Send a buffer to a destination
     */
    bool send(unsigned char* send_buffer, uint32_t send_buffer_size, vbsUDPSocket& socket,
              const Locator_t& remote_locator, bool only_multicast_purpose, bool whitelisted);
#ifdef EDDS_XDP_TRANSPORT_COMPILE
    bool send(unsigned char* send_buffer, uint32_t send_buffer_size, LixdpSocket* lixdp_socket,
              const Locator_t& remote_locator, bool whitelisted);
#endif
    /**
     * @brief Return list of not yet open network interfaces
     *
     * @param [in] sender_resource_list List of SenderResources already registered in the transport
     * @param [out] locNames Return the list of new available network interfaces
     * @param [in] return_loopback return the lo network interface
     */
    void get_unknown_network_interfaces(const SendResourceList& sender_resource_list,
                                        std::vector<IPFinder::info_IP>& locNames, bool return_loopback = false);

    std::atomic_bool rescan_interfaces_ = {true};
#ifdef EDDS_XDP_TRANSPORT_COMPILE
    SocketType socket_type_;
#endif
};

}  // namespace transport
}  // namespace vbs

#endif  // SRC_CPP_RTPS_TRANSPORT_UDPTRANSPORTINTERFACE_H_
